import java.util.Random;

/**
 * This class provides all the genetic operators for building the input example
 * population 
 * 
 *
 */
public class FitnessCalculatorPopulationBuilder {

	Random rand = new Random();
	
	public FitnessCalculatorPopulationBuilder() {
	}

	
	/**
	 * This method extract the best networks from all the networks in the population
	 * 
	 * @param population
	 * @param fitness
	 * @return
	 */
	public SortingNetwork[] extractBestKNetworks(
			SortingNetwork[] population, int[] fitness) {
		SortingNetwork[] newPopulation=new SortingNetwork[Constants.BEST_NETWORKS_NUMBER];
		boolean[] selcted=new boolean[fitness.length];
		
		for(int i=0; i<Constants.BEST_NETWORKS_NUMBER; i++)
		{
			int max=-1;
			int index=-1;
			for(int j=0 ;j<fitness.length; j++)
			{
				if(fitness[j]>max && !selcted[j])
				{
					max=fitness[j];
					index=j;
				}
			}
			selcted[index]=true;
			newPopulation[i]=(population[index]);
		}
		
		
		return newPopulation;
	}
	
	/**
	 * this method calculate the fitness of all the input example
	 * the fitness for every example is the number of the disorder in the
	 * sort for every best networks multiply by the rank of the network
	 * 
	 * @param bestPopulation
	 * @param index
	 * @param inputExamples
	 * @param sortedInputExamples
	 * @return
	 */
	public int calcuateFitenss(SortingNetwork[] bestPopulation,int index,int[][]inputExamples,int[][] sortedInputExamples) {
		double counter=0;
		for (int i=0; i<bestPopulation.length; i++)
		{
			int[] sortedByNetwork = bestPopulation[i].sortInput(inputExamples[index]);
			int didntSorted=0;
			for (int j=0; j<Constants.NETWORK_SIZE_INPUT; j++)
			{
				if(sortedByNetwork[j]!=sortedInputExamples[index][j])
				{
					didntSorted++;
				}
			}
			counter+=(Constants.BEST_NETWORKS_NUMBER-i+1)*didntSorted;
		}
		counter=1000*counter*1.0/Constants.BEST_NETWORKS_NUMBER;
		return (int)counter;
		
	}
	
	/**
	 * the function build the elitism of the next population of the input example
	 * @param population of the input examples
	 * @param newPopulation of the input examples
	 * @param fitness of the input examples
	 * @param elitismCount how much best examples move to next generation 
	 */
	public void selectElitism(int[][] population,int[][] newPopulation,int[] fitness, int elitismCount) {
		boolean[] indexes=new boolean[population.length];
		for(int i=0; i<elitismCount; i++)
		{
			int max=-1;
			int index=-1;
			for(int j=0 ;j<fitness.length; j++)
			{
				if(fitness[j]>max && !indexes[j])
				{
					max=fitness[j];
					index=j;
				}
			}
			indexes[index]=true;
			newPopulation[i]=population[index];
		}
		
	}

	/**
	 * the function build the cross over to the next generation of the input examples
	 * @param inputExamples 2 examples
	 * @param newInputPopulation the new population (for adding it to here
	 * @param inputsFitness
	 * @param fillSize
	 */
	public void doCrossovers(int[][] inputExamples, int[][] newInputPopulation,
			int[] inputsFitness, int fillSize) {
		int fromPosition = Constants.EXAMPLES_NUM-fillSize;
		for (int i = fromPosition; i < Constants.EXAMPLES_NUM; i+=2) {
			int[] p1= getParent(inputExamples,inputsFitness);
			int[] p2= getParent(inputExamples,inputsFitness);

			
			for (int j = 0; j < Constants.NETWORK_SIZE_INPUT; j++) {
				double parent = rand.nextDouble();
				if (parent<=0.5){
					newInputPopulation[i][j]= p1[j];
					newInputPopulation[i+1][j]= p2[j];
				}else{
					newInputPopulation[i][j]= p2[j];
					newInputPopulation[i+1][j]= p1[j];
				}
			}
			mutateInputExample(newInputPopulation[i]);
			mutateInputExample(newInputPopulation[i+1]);
		}
		
		
	}

	/**
	 * make a random desition of a parent to the crossover
	 * the probability of example with bigger fitness is higher:
	 * it sum all the probabilities, random a number and take the example with the same probability 
	 * @param inputExamples all examples
	 * @param inputsFitness the fitness of the examples 
	 * @return
	 */
	private int[] getParent(int[][] inputExamples, int[] inputsFitness) {
		int  sum =0;
		for (int i : inputsFitness) {
			sum+=i;
		}
		
		
		int p = rand.nextInt(sum);
		sum=0;
		for (int i = 0; i < inputsFitness.length; i++) {
			sum+=inputsFitness[i];
			if (p<sum)
				return inputExamples[i];
		}
		
		return inputExamples[inputExamples.length];
	}
	
	/**
	 * make a mutates
	 * @param example that make in her mutate
	 */
	private void mutateInputExample(int[] example) {
		for (int i = 0; i < example.length; i++) {
			double p = rand.nextDouble();
			if (p<Constants.MUTATION_PROBABILITY){
				example[i]=rand.nextInt(Constants.INPUT_NUMBERS_BOUND);
			}
		}
		
	}
}
